package org.lim.shared.upload;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IORuntimeException;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpResponse;
import cn.hutool.http.HttpStatus;
import cn.hutool.http.HttpUtil;
import cn.hutool.http.Method;
import com.aliyun.oss.model.PutObjectResult; // 假设您使用的是阿里云OSS SDK的这个类
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.lim.infrastructure.config.OssClientConfig; // 假设这是您的OSS配置
import org.lim.infrastructure.exception.BusinessException;
import org.lim.infrastructure.exception.ErrorCode;
import org.lim.infrastructure.exception.ThrowUtils;
import org.lim.interfaces.dto.file.UploadPictureResult;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import javax.imageio.ImageIO;
import javax.imageio.ImageReader;
import javax.imageio.ImageWriter;
import javax.imageio.ImageWriteParam;
import javax.imageio.stream.ImageOutputStream;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.time.LocalDate;
import java.util.*;
import java.util.List;

/*
 * @Author: lim
 * @Date: 2025/5/23 14:43
 * Modified: 添加WebP压缩功能, 兼容预签名URL, 调整URL下载限制和校验逻辑
 */

@Service
@Slf4j
public class FileManager {
    /**
     * 1 兆
     */
    private static final long ONE_M = 1024 * 1024L;
    private static final long FOUR_M = 4 * ONE_M; // 为保持一致性定义4MB
    private static final List<String> ALLOW_FORMAT_LIST = Arrays.asList("jpeg", "jpg", "png", "webp");

    // WebP压缩配置
    private static final float WEBP_QUALITY = 0.8f;
    private static final boolean ENABLE_WEBP_CONVERSION = true;

    @Resource
    private OssClientConfig ossClientConfig; // 假设您可能会直接使用或CosManager封装了它

    @Resource
    private CosManager cosManager; // 这是您用于对象存储操作的管理器

    /**
     * 上传文件模板方法
     */
    private UploadPictureResult uploadPictureTemplate(PictureUploadContext context) {
        File uploadFile = null;
        File compressedFile = null;
        try {
            // 1. 验证输入
            validateInput(context);

            // 2. 生成上传路径
            String imagePath = generateUploadPath(context);
            log.info("生成的图片路径: {}", imagePath);
            context.setImagePath(imagePath); // 存储生成的路径到上下文中

            // 3. 创建临时文件
            uploadFile = createTempFile(context);
            log.info("创建的临时文件: {}", uploadFile.getAbsolutePath());

            // 4. 准备上传文件（核心差异化步骤）
            prepareUploadFile(context, uploadFile);

            // 对于URL上传，此时uploadFile包含下载的内容
            // 在压缩前再次确认文件大小和是否为空
            if (uploadFile.length() > FOUR_M) {
                cleanupTempFile(uploadFile); // 清理过大的文件
                throw new BusinessException(ErrorCode.PARAMS_ERROR, "下载的文件大小 (" + FileUtil.readableFileSize(uploadFile.length()) + ") 超过限制 " + (FOUR_M / ONE_M) + "M");
            }
            if (uploadFile.length() == 0) {
                cleanupTempFile(uploadFile); // 清理空文件
                throw new BusinessException(ErrorCode.PARAMS_ERROR, "下载的文件为空");
            }

            // 5. 压缩成WebP格式（或其它格式）
            File finalUploadFile = compressToWebP(uploadFile, context);
            if (finalUploadFile != uploadFile) { // 如果压缩了并且不是同一文件引用
                compressedFile = finalUploadFile; // compressedFile 指向压缩后的文件
                log.info("图片已压缩, 原始大小: {} bytes, 压缩后大小: {} bytes, 最终格式: {}",
                        uploadFile.length(), compressedFile.length(), getFileExtension(compressedFile));
            }

            // 6. 上传到对象存储 (使用context中可能已更新的imagePath)
            PutObjectResult putObjectResult = cosManager.putPictureObject(context.getImagePath(), finalUploadFile);
            log.info("上传成功, ETag: {}", putObjectResult.getETag());

            // 7. 分析并返回结果 (使用context中可能已更新的imagePath)
            String imageName = extractImageName(context);
            return analyzeCosReturn(new AnalyzeCosParams(putObjectResult, imageName, context.getImagePath(), finalUploadFile));

        } catch (Exception e) {
            log.error("FileManager#uploadPictureTemplate 错误, 上下文: {}, 错误: {}",
                    context, ExceptionUtils.getRootCauseMessage(e));
            // 避免重复包装BusinessException
            if (e instanceof BusinessException) {
                throw (BusinessException) e;
            }
            throw new BusinessException(ErrorCode.SYSTEM_ERROR, getErrorMessage(context) + ": " + e.getMessage());
        } finally {
            cleanupTempFile(uploadFile); // 清理原始临时文件
            // 如果compressedFile和uploadFile不是同一个对象，并且compressedFile存在，则清理
            if (compressedFile != null && compressedFile != uploadFile) {
                cleanupTempFile(compressedFile); // 清理压缩后的临时文件
            }
        }
    }

    private File compressToWebP(File originalFile, PictureUploadContext context) {
        if (!ENABLE_WEBP_CONVERSION) {
            return originalFile;
        }

        long originalSize = originalFile.length();
        if (originalSize < 100 * 1024) { // 小于100KB
            log.info("原始文件已足够小 ({}KB), 跳过压缩", originalSize / 1024);
            return originalFile;
        }

        String originalFormat = getImageFormat(originalFile);
        if ("webp".equalsIgnoreCase(originalFormat)) {
            log.info("图片已经是WebP格式, 跳过转换");
            return originalFile;
        }

        File compressedOutputAttemptFile = null; // 用于尝试压缩的文件
        try {
            BufferedImage originalImage = ImageIO.read(originalFile);
            if (originalImage == null) {
                log.warn("无法读取图片进行压缩, 使用原始文件");
                return originalFile;
            }

            // 尝试WebP压缩
            compressedOutputAttemptFile = tryWebPCompression(originalImage, originalFormat);

            // 如果WebP失败或不可用，尝试其他压缩方式
            if (compressedOutputAttemptFile == null || !compressedOutputAttemptFile.exists() || compressedOutputAttemptFile.length() == 0) {
                cleanupTempFile(compressedOutputAttemptFile); // 清理失败的WebP尝试文件
                compressedOutputAttemptFile = tryAlternativeCompression(originalImage, originalFormat);
            }

            // 检查压缩效果
            if (compressedOutputAttemptFile != null && compressedOutputAttemptFile.exists() && compressedOutputAttemptFile.length() > 0) {
                long compressedSize = compressedOutputAttemptFile.length();
                double compressionRatio = (double) compressedSize / originalSize;

                // 如果压缩后文件大小小于原始文件的80%，则使用压缩文件
                if (compressionRatio < 0.8 && compressedSize < originalSize) {
                    String newExtension = getFileExtension(compressedOutputAttemptFile);
                    String updatedPath = updatePathToCompressed(context.imagePath, newExtension);
                    context.setImagePath(updatedPath); // 更新上下文中的路径和扩展名

                    log.info("压缩成功: 原始大小 {} -> 压缩后大小 {} bytes, 压缩率: {:.2f}%, 新路径: {}",
                            originalSize, compressedSize, compressionRatio * 100, updatedPath);
                    return compressedOutputAttemptFile;
                } else {
                    log.info("压缩率过低 ({:.2f}%) 或压缩后更大, 使用原始文件", compressionRatio * 100);
                    cleanupTempFile(compressedOutputAttemptFile); // 清理无效的压缩文件
                    return originalFile;
                }
            } else {
                log.warn("压缩失败, 使用原始文件");
                cleanupTempFile(compressedOutputAttemptFile); // 清理失败的压缩文件
                return originalFile;
            }

        } catch (Exception e) {
            log.error("压缩过程中发生错误: {}, 使用原始文件", e.getMessage(), e);
            cleanupTempFile(compressedOutputAttemptFile); // 清理可能存在的压缩文件
            return originalFile;
        }
    }

    private File tryWebPCompression(BufferedImage image, String originalFormat) {
        try {
            Iterator<ImageWriter> writers = ImageIO.getImageWritersByFormatName("webp");
            if (!writers.hasNext()) {
                log.debug("没有可用的WebP写入器");
                return null;
            }

            File webpFile = File.createTempFile("temp_webp_", ".webp");
            ImageWriter writer = writers.next();

            try (ImageOutputStream ios = ImageIO.createImageOutputStream(webpFile)) {
                writer.setOutput(ios);
                ImageWriteParam param = writer.getDefaultWriteParam();
                if (param.canWriteCompressed()) {
                    param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
                    param.setCompressionQuality(WEBP_QUALITY);
                }
                writer.write(null, new javax.imageio.IIOImage(image, null, null), param);
                log.info("WebP压缩完成");
                return webpFile;
            } finally {
                writer.dispose();
            }
        } catch (Exception e) {
            log.debug("WebP压缩失败: {}", e.getMessage(), e);
            return null;
        }
    }

    private File tryAlternativeCompression(BufferedImage image, String originalFormat) {
        try {
            String targetFormat = "jpg"; // 默认尝试压缩为JPG
            float quality = 0.75f; // JPG的默认压缩质量
            String suffix = ".jpg";

            if ("png".equalsIgnoreCase(originalFormat)) {
                // 对于PNG，如果WebP失败，尝试转换为高质量JPG
                quality = 0.85f;
            }
            // 对于已经是JPG的，如果WebP失败，尝试进一步降低JPG质量
            // 对于其他格式，也尝试转为JPG

            File compressedFile = File.createTempFile("temp_compressed_", suffix);
            Iterator<ImageWriter> writers = ImageIO.getImageWritersByFormatName(targetFormat);
            if (!writers.hasNext()) {
                log.debug("没有可用的 {} 写入器", targetFormat);
                return null;
            }

            ImageWriter writer = writers.next();
            try (ImageOutputStream ios = ImageIO.createImageOutputStream(compressedFile)) {
                writer.setOutput(ios);
                ImageWriteParam param = writer.getDefaultWriteParam();
                if (param.canWriteCompressed()) {
                    param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
                    param.setCompressionQuality(quality);
                }
                // 对于从PNG转换到JPG，可能需要处理透明度，转换为RGB
                BufferedImage imageToCompress = image;
                if ("png".equalsIgnoreCase(originalFormat) && "jpg".equalsIgnoreCase(targetFormat)) {
                    if (image.getTransparency() != BufferedImage.OPAQUE) {
                        imageToCompress = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_INT_RGB);
                        Graphics2D g2d = imageToCompress.createGraphics();
                        g2d.drawImage(image, 0, 0, Color.WHITE, null); // 用白色背景替换透明
                        g2d.dispose();
                        log.debug("PNG转JPG：已将透明背景替换为白色");
                    }
                }
                writer.write(null, new javax.imageio.IIOImage(imageToCompress, null, null), param);
                log.info("备选压缩完成，格式: {}, 质量: {}", targetFormat, quality);
                return compressedFile;
            } finally {
                writer.dispose();
            }
        } catch (Exception e) {
            log.error("备选压缩失败: {}", e.getMessage(), e);
            return null;
        }
    }

    private String getFileExtension(File file) {
        String fileName = file.getName();
        int lastDot = fileName.lastIndexOf('.');
        if (lastDot > 0 && lastDot < fileName.length() - 1) {
            return fileName.substring(lastDot + 1).toLowerCase();
        }
        return ""; // 如果没有扩展名，返回空字符串
    }

    private String extractDominantColor(BufferedImage image) {
        try {
            int scaledWidth = Math.min(image.getWidth(), 150);
            int scaledHeight = Math.min(image.getHeight(), 150);
            BufferedImage scaledImage = new BufferedImage(scaledWidth, scaledHeight, BufferedImage.TYPE_INT_RGB);
            scaledImage.getGraphics().drawImage(image.getScaledInstance(scaledWidth, scaledHeight, Image.SCALE_SMOOTH), 0, 0, null);

            Map<Integer, Integer> colorCount = new HashMap<>();
            for (int x = 0; x < scaledWidth; x++) {
                for (int y = 0; y < scaledHeight; y++) {
                    int rgb = scaledImage.getRGB(x, y);
                    Color color = new Color(rgb);
                    if (isValidColor(color)) {
                        int simplifiedRgb = simplifyColor(rgb);
                        colorCount.put(simplifiedRgb, colorCount.getOrDefault(simplifiedRgb, 0) + 1);
                    }
                }
            }

            if (colorCount.isEmpty()) return "#808080"; // 如果没有有效颜色，返回默认灰色

            int dominantColor = colorCount.entrySet().stream()
                    .max(Map.Entry.comparingByValue())
                    .map(Map.Entry::getKey)
                    .orElse(0x808080);
            return String.format("#%06X", dominantColor & 0xFFFFFF);
        } catch (Exception e) {
            log.error("提取主色调错误: {}", e.getMessage(), e);
            return "#808080";
        }
    }

    private boolean isValidColor(Color color) {
        int r = color.getRed();
        int g = color.getGreen();
        int b = color.getBlue();
        if (r > 240 && g > 240 && b > 240) return false; // 太白
        if (r < 15 && g < 15 && b < 15) return false;   // 太黑
        return true;
    }

    private int simplifyColor(int rgb) {
        int r = (rgb >> 16) & 0xFF;
        int g = (rgb >> 8) & 0xFF;
        int b = rgb & 0xFF;
        r = (r / 8) * 8; g = (g / 8) * 8; b = (b / 8) * 8; // 量化到32级
        return (r << 16) | (g << 8) | b;
    }

    private String updatePathToCompressed(String originalPath, String newExtension) {
        if (StringUtils.isBlank(originalPath) || StringUtils.isBlank(newExtension)) {
            return originalPath;
        }
        String cleanNewExtension = newExtension.startsWith(".") ? newExtension.substring(1) : newExtension;
        int lastDot = originalPath.lastIndexOf('.');
        if (lastDot > 0) {
            return originalPath.substring(0, lastDot + 1) + cleanNewExtension;
        }
        return originalPath + "." + cleanNewExtension; // 如果原始路径没有点，则直接追加
    }

    private String getImageFormat(File imageFile) {
        try (FileInputStream fis = new FileInputStream(imageFile)) {
            // 尝试从文件流中读取格式，更可靠
            javax.imageio.stream.ImageInputStream iis = ImageIO.createImageInputStream(fis);
            if (iis != null) {
                Iterator<ImageReader> readers = ImageIO.getImageReaders(iis);
                if (readers.hasNext()) {
                    ImageReader reader = readers.next();
                    String formatName = reader.getFormatName().toLowerCase();
                    reader.dispose();
                    iis.close();
                    if (StrUtil.isNotBlank(formatName)) return formatName;
                }
                if (iis != null) iis.close(); // 确保关闭
            }
        } catch (IOException e) {
            log.debug("通过ImageIO获取图片格式失败: {}", e.getMessage());
        }
        // 如果上述方法失败，回退到基于文件扩展名
        String extension = getFileExtension(imageFile);
        log.debug("回退到使用文件扩展名作为图片格式: {}", extension);
        return extension; // 可能为空字符串
    }


    public UploadPictureResult uploadPicture(MultipartFile multipartFile, String uploadPathPrefix) {
        PictureUploadContext context = new MultipartFileUploadContext(multipartFile, uploadPathPrefix);
        return uploadPictureTemplate(context);
    }

    public UploadPictureResult uploadPictureByUrl(String fileUrl, String uploadPathPrefix) {
        PictureUploadContext context = new UrlUploadContext(fileUrl, uploadPathPrefix);
        return uploadPictureTemplate(context);
    }

    private void validateInput(PictureUploadContext context) { context.validate(); }
    private String generateUploadPath(PictureUploadContext context) { return context.generateUploadPath(); }
    private File createTempFile(PictureUploadContext context) throws Exception { return context.createTempFile(); }
    private void prepareUploadFile(PictureUploadContext context, File uploadFile) throws Exception { context.prepareUploadFile(uploadFile); }
    private String extractImageName(PictureUploadContext context) { return context.extractImageName(); }
    private String getErrorMessage(PictureUploadContext context) { return context.getErrorMessage(); }

    private void cleanupTempFile(File tempFile) {
        if (tempFile != null && tempFile.exists()) { // 增加exists检查
            try {
                boolean deleted = FileUtil.del(tempFile); // Hutool的删除方法
                if (deleted) {
                    log.debug("已删除临时文件: {}", tempFile.getAbsolutePath());
                } else {
                    log.warn("未能删除临时文件 (可能已被其他进程锁定或不存在): {}", tempFile.getAbsolutePath());
                }
            } catch (IORuntimeException e) { // Hutool可能抛出的异常
                log.error("删除临时文件失败: {}, 错误: {}",
                        tempFile.getAbsolutePath(), ExceptionUtils.getRootCauseMessage(e), e);
            }
        }
    }

    private abstract static class PictureUploadContext {
        protected String uploadPathPrefix;
        protected String imagePath; // 这个路径会在压缩后更新

        public PictureUploadContext(String uploadPathPrefix) {
            this.uploadPathPrefix = uploadPathPrefix;
        }

        protected abstract void validate();
        protected abstract String generateUploadPath();
        protected abstract File createTempFile() throws Exception;
        protected abstract void prepareUploadFile(File uploadFile) throws Exception;
        protected abstract String extractImageName();
        protected abstract String getErrorMessage();

        protected String processUploadPathPrefix(String prefix) {
            String cleanPrefix = StringUtils.defaultIfBlank(prefix, "images");
            cleanPrefix = StringUtils.stripStart(cleanPrefix, "/");
            cleanPrefix = StringUtils.stripEnd(cleanPrefix, "/");
            return cleanPrefix;
        }

        protected String generateUniqueFileName(String fileSuffix) {
            // 如果启用了WebP转换，初始文件名后缀统一为webp，实际后缀可能在压缩后改变
            String finalSuffix = ENABLE_WEBP_CONVERSION ? "webp" : StringUtils.defaultIfBlank(fileSuffix, "jpg");

            return String.format("%s_%s_%s.%s",
                    LocalDate.now().toString().replace("-", ""),
                    System.currentTimeMillis(),
                    UUID.randomUUID().toString().substring(0, 8),
                    finalSuffix);
        }
        public String getImagePath() { return imagePath; }
        public void setImagePath(String imagePath) { this.imagePath = imagePath; }
    }

    private class MultipartFileUploadContext extends PictureUploadContext {
        private final MultipartFile multipartFile;
        public MultipartFileUploadContext(MultipartFile multipartFile, String uploadPathPrefix) {
            super(uploadPathPrefix);
            this.multipartFile = multipartFile;
        }
        @Override protected void validate() { validPicture(multipartFile); }
        @Override protected String generateUploadPath() {
            String originalFilename = multipartFile.getOriginalFilename();
            ThrowUtils.throwIf(StringUtils.isBlank(originalFilename), ErrorCode.PARAMS_ERROR, "文件名不能为空");
            String fileSuffix = FileUtil.getSuffix(originalFilename);
            ThrowUtils.throwIf(StringUtils.isBlank(fileSuffix), ErrorCode.PARAMS_ERROR, "文件后缀不能为空");
            String uniqueFileName = generateUniqueFileName(fileSuffix);
            String cleanPrefix = processUploadPathPrefix(uploadPathPrefix);
            return cleanPrefix + "/" + uniqueFileName;
        }
        @Override protected File createTempFile() throws Exception {
            String suffix = FileUtil.getSuffix(multipartFile.getOriginalFilename());
            return File.createTempFile("upload_", "." + (StringUtils.isNotBlank(suffix) ? suffix : "tmp"));
        }
        @Override protected void prepareUploadFile(File uploadFile) throws Exception { multipartFile.transferTo(uploadFile); }
        @Override protected String extractImageName() { return FileUtil.mainName(multipartFile.getOriginalFilename()); }
        @Override protected String getErrorMessage() { return "上传图片(MultipartFile)失败"; }
        @Override public String toString() {
            return String.format("MultipartFileUploadContext{fileName='%s', size=%d, prefix='%s'}",
                    multipartFile.getOriginalFilename(), multipartFile.getSize(), uploadPathPrefix);
        }
    }

    private class UrlUploadContext extends PictureUploadContext {
        private final String fileUrl;
        public UrlUploadContext(String fileUrl, String uploadPathPrefix) {
            super(uploadPathPrefix);
            this.fileUrl = fileUrl;
        }
        @Override protected void validate() { validPicture(fileUrl); }
        @Override protected String generateUploadPath() {
            String fileSuffix = inferSuffixFromUrl(fileUrl); // 优先使用推断的后缀
            if (StringUtils.isBlank(fileSuffix)) { // 如果推断失败，尝试从URL路径获取
                try {
                    fileSuffix = FileUtil.getSuffix(new URL(fileUrl).getPath());
                } catch (MalformedURLException e) {
                    log.warn("从URL路径获取后缀失败: {}", fileUrl);
                }
            }
            ThrowUtils.throwIf(StringUtils.isBlank(fileSuffix) || !ALLOW_FORMAT_LIST.contains(fileSuffix.toLowerCase()),
                    ErrorCode.PARAMS_ERROR, "无法从URL确定有效的文件类型: " + fileUrl);
            String uniqueFileName = generateUniqueFileName(fileSuffix);
            String cleanPrefix = processUploadPathPrefix(uploadPathPrefix);
            return cleanPrefix + "/" + uniqueFileName;
        }
        @Override protected File createTempFile() throws Exception {
            String suffix = inferSuffixFromUrl(fileUrl);
            if (StringUtils.isBlank(suffix)) {
                try {
                    suffix = FileUtil.getSuffix(new URL(fileUrl).getPath());
                } catch (MalformedURLException e) { /* ignore */ }
            }
            return File.createTempFile("upload_url_", "." + (StringUtils.isNotBlank(suffix) ? suffix : "tmp"));
        }
        @Override protected void prepareUploadFile(File uploadFile) throws Exception { downloadFileFromUrl(fileUrl, uploadFile); }
        @Override protected String extractImageName() { return extractImageNameFromUrl(fileUrl); }
        @Override protected String getErrorMessage() { return "通过URL上传图片失败"; }
        @Override public String toString() {
            return String.format("UrlUploadContext{fileUrl='%s', prefix='%s'}", fileUrl, uploadPathPrefix);
        }
    }

    private String extractImageNameFromUrl(String fileUrl) {
        try {
            URL url = new URL(fileUrl);
            String path = url.getPath();
            if (StringUtils.isBlank(path) || "/".equals(path)) {
                return "image_from_url_" + System.currentTimeMillis(); // 添加时间戳避免重复
            }
            String fileName = path.substring(path.lastIndexOf('/') + 1);
            // 移除查询参数部分（如果存在）
            int queryParamStartIndex = fileName.indexOf('?');
            if (queryParamStartIndex != -1) {
                fileName = fileName.substring(0, queryParamStartIndex);
            }
            if (StringUtils.isBlank(fileName)) {
                return "image_from_url_" + System.currentTimeMillis();
            }
            String mainName = FileUtil.mainName(fileName);
            return StringUtils.isNotBlank(mainName) ? mainName : "image_from_url_" + System.currentTimeMillis();
        } catch (Exception e) {
            log.warn("从URL提取图片名称失败: {}, 使用默认名称", fileUrl, e);
            return "image_from_url_" + System.currentTimeMillis();
        }
    }

    private String inferSuffixFromUrl(String fileUrl) {
        String suffixFromPath = "";
        try {
            URL url = new URL(fileUrl);
            String path = url.getPath();
            if (StringUtils.isNotBlank(path)) {
                suffixFromPath = FileUtil.getSuffix(path);
                if (StringUtils.isNotBlank(suffixFromPath)) {
                    suffixFromPath = suffixFromPath.toLowerCase();
                }
            }
        } catch (MalformedURLException e) {
            log.warn("推断后缀时URL格式错误: {}, 错误: {}", fileUrl, e.getMessage());
        }

        HttpResponse response = null;
        try {
            response = HttpUtil.createRequest(Method.HEAD, fileUrl).timeout(5000).execute(); // 增加超时
            if (response.getStatus() == HttpStatus.HTTP_OK) {
                String contentType = response.header("Content-Type");
                if (StringUtils.isNotBlank(contentType)) {
                    contentType = contentType.toLowerCase();
                    if (contentType.contains("jpeg") || contentType.contains("jpg")) return "jpg";
                    if (contentType.contains("png")) return "png";
                    if (contentType.contains("webp")) return "webp";
                    log.warn("未知的Content-Type: {} (URL: {}). 将尝试路径后缀或默认值.", contentType, fileUrl);
                }
            } else {
                log.warn("HEAD请求获取后缀失败，状态码: {} (URL: {}). 将尝试路径后缀或默认值.", response.getStatus(), fileUrl);
            }
        } catch (Exception e) {
            log.warn("通过HEAD请求推断后缀失败: URL={}, 错误: {}. 将尝试路径后缀或默认值.", fileUrl, e.getMessage());
        } finally {
            if (response != null) response.close();
        }

        if (StringUtils.isNotBlank(suffixFromPath) && ALLOW_FORMAT_LIST.contains(suffixFromPath)) {
            log.debug("使用URL路径中的后缀 '{}' (URL: {})", suffixFromPath, fileUrl);
            return suffixFromPath;
        }
        log.warn("无法从HEAD或路径推断后缀 (URL: {}), 默认为 'jpg'.", fileUrl);
        return "jpg";
    }

    private void downloadFileFromUrl(String fileUrl, File targetFile) {
        HttpResponse downloadResponse = null;
        try {
            downloadResponse = HttpUtil.createGet(fileUrl).timeout(30000).execute();
            if (downloadResponse.getStatus() != HttpStatus.HTTP_OK) {
                throw new BusinessException(ErrorCode.SYSTEM_ERROR,
                        String.format("下载文件失败，HTTP状态码: %d, URL: %s", downloadResponse.getStatus(), fileUrl));
            }
            FileUtil.writeFromStream(downloadResponse.bodyStream(), targetFile);
            long fileSize = targetFile.length();
            if (fileSize <= 0) { // 在uploadPictureTemplate中已检查，此处作为额外防御
                throw new BusinessException(ErrorCode.SYSTEM_ERROR, "下载的文件为空, URL: " + fileUrl);
            }
            if (fileSize > FOUR_M) { // 在uploadPictureTemplate中已检查，此处作为额外防御
                throw new BusinessException(ErrorCode.PARAMS_ERROR, "下载的文件大小(" + FileUtil.readableFileSize(fileSize) + ")超过限制 " + (FOUR_M / ONE_M) + "M, URL: " + fileUrl);
            }
            log.info("成功从URL下载文件: {}, 大小: {} bytes", fileUrl, fileSize);
        } catch (BusinessException e) {
            throw e;
        } catch (Exception e) {
            log.error("从URL下载文件失败: {}, 错误: {}", fileUrl, ExceptionUtils.getRootCauseMessage(e), e);
            throw new BusinessException(ErrorCode.SYSTEM_ERROR, "下载文件失败: " + e.getMessage());
        } finally {
            if (downloadResponse != null) downloadResponse.close();
        }
    }

    private UploadPictureResult analyzeCosReturn(AnalyzeCosParams params) {
        try {
            BufferedImage image = ImageIO.read(params.getUploadFile());
            if (image == null) {
                throw new BusinessException(ErrorCode.PARAMS_ERROR, "无法读取图片信息，可能不是有效的图片文件: " + params.getImageName());
            }
            int width = image.getWidth();
            int height = image.getHeight();
            long fileSize = params.getUploadFile().length();
            // 从最终的imagePath获取格式，因为它可能已被压缩逻辑更改
            String format = FileUtil.getSuffix(params.getImagePath());
            if (StringUtils.isBlank(format)) { // 如果路径中没有后缀，尝试从文件名获取（不太可能发生）
                format = FileUtil.getSuffix(params.getImageName());
            }
            if (StringUtils.isBlank(format)) { // 如果还是没有，使用解析出的图片格式
                format = getImageFormat(params.getUploadFile());
            }

            String dominantColor = extractDominantColor(image);
            log.info("图片信息 - 宽度: {}, 高度: {}, 大小: {}, 格式: {}, 主色调: {}, 路径: {}",
                    width, height, FileUtil.readableFileSize(fileSize), format, dominantColor, params.getImagePath());

            return UploadPictureResult.builder()
                    .picFormat(StringUtils.defaultIfBlank(format, "unknown"))
                    .picHeight(height)
                    .picWidth(width)
                    .picSize(fileSize)
                    .picScale(width == 0 ? 0 : NumberUtil.round(height * 1.0 / width, 2).doubleValue()) // 避免除以零
                    .picName(params.getImageName())
                    .picColor(dominantColor)
                    .url(String.format("%s/%s", cosManager.getBaseUrl(), params.getImagePath()))
                    .build();
        } catch (Exception e) {
            log.error("analyzeCosReturn 错误: {}", ExceptionUtils.getRootCauseMessage(e), e);
            throw new BusinessException(ErrorCode.SYSTEM_ERROR, "分析图片信息失败: " + e.getMessage());
        }
    }

    public void validPicture(MultipartFile multipartFile) {
        ThrowUtils.throwIf(multipartFile == null, ErrorCode.PARAMS_ERROR, "文件不能为空");
        ThrowUtils.throwIf(StringUtils.isBlank(multipartFile.getOriginalFilename()), ErrorCode.PARAMS_ERROR, "文件名不能为空");
        long fileSize = multipartFile.getSize();
        ThrowUtils.throwIf(fileSize <= 0, ErrorCode.PARAMS_ERROR, "文件内容不能为空");
        ThrowUtils.throwIf(fileSize > FOUR_M, ErrorCode.PARAMS_ERROR, "文件大小(" + FileUtil.readableFileSize(fileSize) + ")不能超过" + (FOUR_M / ONE_M) + "M");
        String fileSuffix = FileUtil.getSuffix(multipartFile.getOriginalFilename());
        ThrowUtils.throwIf(StringUtils.isBlank(fileSuffix), ErrorCode.PARAMS_ERROR, "文件类型不能为空");
        ThrowUtils.throwIf(!ALLOW_FORMAT_LIST.contains(fileSuffix.toLowerCase()), ErrorCode.PARAMS_ERROR,
                String.format("不支持的文件类型: %s，支持的类型: %s", fileSuffix, ALLOW_FORMAT_LIST));
    }

    private void validPicture(String fileUrl) {
        ThrowUtils.throwIf(StrUtil.isBlank(fileUrl), ErrorCode.PARAMS_ERROR, "文件地址不能为空");
        URL urlObj;
        try {
            urlObj = new URL(fileUrl);
        } catch (MalformedURLException e) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "文件地址格式不正确: " + fileUrl);
        }
        ThrowUtils.throwIf(!("http".equalsIgnoreCase(urlObj.getProtocol()) || "https".equalsIgnoreCase(urlObj.getProtocol())),
                ErrorCode.PARAMS_ERROR, "仅支持 HTTP 或 HTTPS 协议的文件地址");

        HttpResponse response = null;
        boolean headRequestSuccessfulAndInformative = false;
        try {
            response = HttpUtil.createRequest(Method.HEAD, fileUrl).timeout(5000).execute();
            if (response.getStatus() == HttpStatus.HTTP_OK) {
                String contentType = response.header("Content-Type");
                boolean contentTypeValid = false;
                if (StrUtil.isNotBlank(contentType)) {
                    final List<String> ALLOW_CONTENT_TYPES = Arrays.asList("image/jpeg", "image/jpg", "image/png", "image/webp");
                    contentTypeValid = ALLOW_CONTENT_TYPES.stream().anyMatch(allowedType -> contentType.toLowerCase().startsWith(allowedType));
                    ThrowUtils.throwIf(!contentTypeValid, ErrorCode.PARAMS_ERROR, "文件类型错误 (Content-Type: " + contentType + ")");
                } else { log.warn("HEAD请求 {} 成功但无Content-Type头.", fileUrl); }

                String contentLengthStr = response.header("Content-Length");
                boolean contentLengthValid = false;
                if (StrUtil.isNotBlank(contentLengthStr)) {
                    try {
                        long contentLength = Long.parseLong(contentLengthStr);
                        ThrowUtils.throwIf(contentLength == 0, ErrorCode.PARAMS_ERROR, "文件内容(据HEAD请求)不能为空");
                        ThrowUtils.throwIf(contentLength > FOUR_M, ErrorCode.PARAMS_ERROR,
                                "文件大小(据HEAD: " + FileUtil.readableFileSize(contentLength) + ")不能超过 " + (FOUR_M / ONE_M) + "M");
                        contentLengthValid = true;
                    } catch (NumberFormatException e) {
                        throw new BusinessException(ErrorCode.PARAMS_ERROR, "文件大小格式错误 (Content-Length from HEAD)");
                    }
                } else { log.warn("HEAD请求 {} 成功但无Content-Length头.", fileUrl); }
                if (contentTypeValid && contentLengthValid) headRequestSuccessfulAndInformative = true;
            } else {
                log.warn("URL验证的HEAD请求失败: URL={}, 状态={}. 将依赖下载和解析进行验证.", fileUrl, response.getStatus());
            }
        } catch (Exception e) {
            log.warn("URL验证的HEAD请求异常: URL={}, 错误={}. 将依赖下载和解析进行验证.", fileUrl, e.getMessage(), e);
        } finally {
            if (response != null) response.close();
        }

        if (!headRequestSuccessfulAndInformative) { // 如果HEAD不成功或信息不全
            String path = urlObj.getPath();
            if (StringUtils.isNotBlank(path)) {
                String suffixFromPath = FileUtil.getSuffix(path);
                if (StrUtil.isNotBlank(suffixFromPath)) {
                    ThrowUtils.throwIf(!ALLOW_FORMAT_LIST.contains(suffixFromPath.toLowerCase()), ErrorCode.PARAMS_ERROR,
                            String.format("不支持的文件类型 (据URL扩展名): %s，支持的类型: %s", suffixFromPath, ALLOW_FORMAT_LIST));
                    log.info("HEAD请求信息不全, 但URL路径后缀 '{}' 在允许列表中 (URL: {})", suffixFromPath, fileUrl);
                } else {
                    log.warn("无法从HEAD或URL路径扩展名确定文件类型 {}. 下载将尝试, 但可能后续验证失败.", fileUrl);
                }
            } else {
                log.warn("URL路径为空 {}. 无法通过扩展名验证类型. 依赖下载.", fileUrl);
            }
        }
    }

    // 注意：旧的 deleteTempFile(File file) 方法已被 cleanupTempFile(File tempFile) 替代和增强，可以移除。
}

/**
 * 分析参数类 (无变化)
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
class AnalyzeCosParams {
    private PutObjectResult putObjectResult;
    private String imageName;
    private String imagePath; // 确保这是最终上传到COS的路径
    private File uploadFile;  // 这是最终上传到COS的文件（可能是压缩后的）
}
